/**
 * \file  full_test.cpp
 * \brief implementation of test to AProba Algorithm methods.
 *
 * Created on: May 13, 2013
 *
 * Author: Boretto Martin, Lenarduzzi Federico
 * E-Mail:
 *
 */

#include <string>
#include <iostream>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include "lav/antiviral_imp.h"
#include "lav/antiviral_selector.h"
#include "lav/antiviral_set.h"
#include "lav/antiviral.h"
#include "lav/aso_exception.h"
#include "lav/data_base.h"
#include "lav/distance_matrix.h"
#include "lav/optimized_container.h"
#include "lav/sequence.h"
#include "lav/debugging.h"


using namespace lav;
using namespace std;

/**
 * This class defines the setup of the tests
 *
 */
class FullTest : public ::testing::Test
{

public:

    int algorithm_step;
    DataBase db;
    biopp::PseudoNucSequence sequence;
    Debugging debug;
    // Used in the STEP 1
    AntiviralSet my_antivirals;
    // Used in the STEP 2 and STEP 3
    ResisBasedAntiviral::CombinationContainer arvs_info;
    ResisBasedAntiviral::CombinationContainer arvs_info_x;
    // Used in the STEP 4 and STEP 5
    ARVExpansionContainer combinations_expanded;
    ARVExpansionContainer combinations_expanded_with_cp;
    // Used in the STEP 6 and STEP 7
    CombinationDistanceContainer combinations_with_distances;
    CombinationDistanceContainer combinations_with_distances_reduce;
    // Used in the STEP 8
    MutantContainer mutants_list;

    // Constructor
    FullTest() {};


    /**
     * This method is executed before each test
     *
     */
    void SetUp()
    {
        db.load_file("./ref/AProba/antivirals_AProba_2.xml");
        db.get_antivirals(my_antivirals);
        load_sequence("./ref/AProba/init_seq_AProba.txt", sequence);
        set_default_matrix(_matrix);
    }


    /**
     * This method is executed after each test
     *
     */
    void TearDown()
    {
        // If all ASSERTs are OK, the information will be print depending of the stage
        switch (algorithm_step)
        {
            case(1):
                debug.print_antivirals(my_antivirals);
                break;

            case(2):
                debug.print_all_combinations(arvs_info, false);
                break;

            case(3):
                debug.print_all_combinations(arvs_info_x, true);
                break;

            case(4):
                debug.print_expansions(combinations_expanded, false);
                break;

            case(5):
                debug.print_expansions(combinations_expanded_with_cp, true);
                break;

            case(6):
                debug.print_combinations_with_genetic_distances(combinations_with_distances, false);
                break;

            case(7):
                debug.print_combinations_with_genetic_distances(combinations_with_distances_reduce, true);
                break;

            case(8):
                debug.print_all_mutants(mutants_list);
                break;

            default:
                clog << "\n\n End of AProba Algorithm... \n\n" << endl;
                break;
        }
    }
};


//Arvs_Set AntiviralSet::antivirals;

/**
 * This unit test checks the pre_processing method
 *
 */
TEST_F(FullTest, Unit_Test_preprocessing)
{

    ASSERT_EQ(my_antivirals.size(), 2);

    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    int cont_groups;
    AVIterator it_av = my_antivirals.begin_AVIterator();;
    while (!it_av.end())
    {
        cont_groups = 0;
        string av_name;
        (*it_av)->get_attrib("name", av_name);
        ResisBasedAntiviral::ResistancesGroupContainer::iterator it_groups;
        ResisBasedAntiviral::ResistancesGroupContainer& current_group = (*it_av)->resGroups;
        for (it_groups = current_group.begin(); it_groups != current_group.end(); ++it_groups)
        {
            // Processing Groups
            ++cont_groups;
            if (av_name == "NRTI3" && cont_groups == 2)
            {
                // Expected results after pre_processing running
                ASSERT_EQ(it_groups->minResistanceWeight, 1.7);
                ASSERT_EQ(it_groups->resistances.size(), 2);
            }
            else
            {
                if (av_name == "NRTI1" && cont_groups == 1)
                {
                    // Expected results after pre_processing running
                    ASSERT_EQ(it_groups->minResistanceWeight, 2.0);
                    ASSERT_EQ(it_groups->resistances.size(), 2);
                }
            }
        }
        ++it_av;
    }

    // This is neccesary to print the correct information
    algorithm_step = 1;

}


/**
 * This unit test checks the generate_power_set method
 *
 */
TEST_F(FullTest, Unit_Test_generate_power_set)
{

    int index_combinations;
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);
    // STEP 2 AND 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    ASSERT_NE(my_antivirals.size(), 0);

    ResisBasedAntiviral::CombinationContainer::const_iterator it_comb_ps;
    index_combinations = 1;
    for (it_comb_ps = arvs_info.begin(); it_comb_ps != arvs_info.end(); ++it_comb_ps)
    {

        ResisBasedAntiviral::Combination::const_iterator it_comb;
        ResisBasedAntiviral::Combination current_combination = (*it_comb_ps);

        for (it_comb = current_combination.begin(); it_comb != current_combination.end(); ++it_comb)
        {
            // Depending on the group from it comes, the list of combinations will be 'x'
            switch (index_combinations)
            {
                    // NRTI3
                case(1):
                    // The size of the current list of combinations should be 1
                    ASSERT_EQ(it_comb->size(), 1);
                    break;

                case(2):
                    // The size of the current list of combinations should be 1
                    ASSERT_EQ(it_comb->size(), 1);
                    break;

                    // NRTI1
                case(3):
                    // The size of the current list of combinations should be 2
                    ASSERT_EQ(it_comb->size(), 2);
                    break;

                case(4):
                    // The size of the current list of combinations should be 2
                    ASSERT_EQ(it_comb->size(), 2);
                    break;
                case(5):
                    // The size of the current list of combinations should be 2
                    ASSERT_EQ(it_comb->size(), 2);
                    break;
                case(6):
                    // The size of the current list of combinations should be 2
                    ASSERT_EQ(it_comb->size(), 2);
                    break;
            }
            ++index_combinations;
        }
    }

    // This is neccesary to print the correct information
    algorithm_step = 2;
}


/**
 * This unit test checks the generate_cartesian_product method
 *
 */
TEST_F(FullTest, Unit_Test_generate_cartesian_product)
{
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    ResisBasedAntiviral::CombinationContainer::const_iterator it_comb_ps;
    for (it_comb_ps = arvs_info_x.begin(); it_comb_ps != arvs_info_x.end(); ++it_comb_ps)
        // In this particular case, all cartesian product's combinations should have 3 elements
        ASSERT_EQ(it_comb_ps->size(), 3);

    // This is neccesary to print the correct information
    algorithm_step = 3;
}


/**
 * This unit test checks the expand_to_nucleotids method
 *
 */
TEST_F(FullTest, Unit_Test_expand_to_nucleotids)
{
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    // STEP 5
    my_antivirals.combinator.expand_to_nucleotids(sequence, arvs_info_x, combinations_expanded);

    ARVExpansionContainer::const_iterator it_comb_exp;
    for (it_comb_exp = combinations_expanded.begin(); it_comb_exp != combinations_expanded.end(); ++it_comb_exp)
    {
        ARVExpansion::const_iterator it_second;
        ARVExpansion second = (*it_comb_exp);
        for (it_second = second.begin(); it_second != second.end(); ++it_second)
        {

            AminoacidExpansion first = (*it_second);
            AminoacidExpansion::const_iterator it_first = first.begin();

            switch (it_first->pos)
            {
                case(0):
                    // This position belongs to aminoacid 'I'.
                    // This aminoacid should be expands to 3 codons
                    ASSERT_EQ(it_second->size(), 3);
                    break;

                case(3):
                    // This position belongs to aminoacid 'V'
                    // This aminoacid should be expands to 4 codons
                    ASSERT_EQ(it_second->size(), 4);
                    break;

                case(4):
                    // This position belongs to aminoacid 'R'
                    // This aminoacid should be expands to 6 codons
                    ASSERT_EQ(it_second->size(), 6);
                    break;

                case(6):
                    // This position belongs to aminoacid 'R'
                    // This aminoacid should be expands to 6 codons
                    ASSERT_EQ(it_second->size(), 6);
                    break;

                case(8):
                    // This position belongs to aminoacid 'T'
                    // This aminoacid should be expands to 4 codons
                    ASSERT_EQ(it_second->size(), 4);
                    break;

                case(9):
                    // This position belongs to aminoacid 'V'
                    // This aminoacid should be expands to 4 codons
                    ASSERT_EQ(it_second->size(), 4);
                    break;

                case(11):
                    // This position belongs to aminoacid 'I'
                    // This aminoacid should be expands to 3 codons
                    ASSERT_EQ(it_second->size(), 3);
                    break;
            }
        }
        // In this particular case, all expansions combinations should have 3 elements
        ASSERT_EQ(it_comb_exp->size(), 3);
    }

    // This is neccesary to print the correct information
    algorithm_step = 4;
}


/**
 * This unit test checks the generate_triplets_cartesian_product method
 *
 */
TEST_F(FullTest, Unit_Test_generate_triplets_cartesian_product)
{
    int cont_expansions, total_expansions;

    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    // STEP 5
    my_antivirals.combinator.expand_to_nucleotids(sequence, arvs_info_x, combinations_expanded);

    my_antivirals.combinator.generate_triplets_cartesian_product(combinations_expanded, combinations_expanded_with_cp);

    cont_expansions = 1;
    total_expansions = 0;
    ARVExpansionContainer::const_iterator it_comb_exp;
    for (it_comb_exp = combinations_expanded.begin(); it_comb_exp != combinations_expanded.end(); ++it_comb_exp)
    {
        cont_expansions = 1;
        ARVExpansion::const_iterator it_second;
        ARVExpansion second = (*it_comb_exp);
        for (it_second = second.begin(); it_second != second.end(); ++it_second)
        {
            cont_expansions *= it_second->size();
        }
        total_expansions += cont_expansions;
    }

    // This should be the total of combinations after applies nucleotids cartesian product
    ASSERT_EQ(total_expansions, 576);

    // This is neccesary to print the correct information
    algorithm_step = 5;
}


/**
 * This unit test checks the calculate_genetic_distance method
 *
 */
TEST_F(FullTest, Unit_Test_calculate_genetic_distance)
{
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    // STEP 5
    my_antivirals.combinator.expand_to_nucleotids(sequence, arvs_info_x, combinations_expanded);
    my_antivirals.combinator.generate_triplets_cartesian_product(combinations_expanded, combinations_expanded_with_cp);

    // STEP 6
    my_antivirals.ranker.calculate_genetic_distance(sequence, _matrix, combinations_expanded_with_cp, combinations_with_distances);

    CombinationDistanceContainer::const_iterator it_combinations;
    for (it_combinations = combinations_with_distances_reduce.begin(); it_combinations != combinations_with_distances_reduce.end(); ++it_combinations)
    {
        // All combinations, should have a genetic distance between 3 and 9
        ASSERT_LE(it_combinations->distance, 9);
        ASSERT_GE(it_combinations->distance, 3);
    }

    // This should be the total of combinations after applies calculate genetic distance method
    ASSERT_EQ(combinations_with_distances.size(), 576);

    // This is neccesary to print the correct information
    algorithm_step = 6;
}


/**
 * This unit test checks the rank_combinations method
 *
 */
TEST_F(FullTest, Unit_Test_rank_combinations)
{
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    // STEP 5
    my_antivirals.combinator.expand_to_nucleotids(sequence, arvs_info_x, combinations_expanded);
    my_antivirals.combinator.generate_triplets_cartesian_product(combinations_expanded, combinations_expanded_with_cp);

    // STEP 6
    my_antivirals.ranker.calculate_genetic_distance(sequence, _matrix, combinations_expanded_with_cp, combinations_with_distances);

    // STEP 7
    my_antivirals.ranker.rank_combinations(combinations_with_distances, combinations_with_distances_reduce);

    CombinationDistanceContainer::const_iterator it_combinations;
    for (it_combinations = combinations_with_distances_reduce.begin(); it_combinations != combinations_with_distances_reduce.end(); ++it_combinations)
        // All combinations, should have a genetic distance at least 4
        ASSERT_LE(it_combinations->distance, 4);

    // According to the information we have, the size ranker should be this
    ASSERT_EQ(combinations_with_distances_reduce.size(), 34);

    // This is neccesary to print the correct information
    algorithm_step = 7;

}


/**
 * This unit test checks the generate_mutants method
 *
 */
TEST_F(FullTest, Unit_Test_generate_mutants)
{
    // STEP 1
    my_antivirals.combinator.pre_processing(sequence);

    // STEP 2 Y 3
    my_antivirals.combinator.generate_power_set(arvs_info);

    // STEP 4
    my_antivirals.combinator.generate_cartesian_product(arvs_info, arvs_info_x);

    // STEP 5
    my_antivirals.combinator.expand_to_nucleotids(sequence, arvs_info_x, combinations_expanded);
    my_antivirals.combinator.generate_triplets_cartesian_product(combinations_expanded, combinations_expanded_with_cp);

    // STEP 6
    my_antivirals.ranker.calculate_genetic_distance(sequence, _matrix, combinations_expanded_with_cp, combinations_with_distances);

    // STEP 7
    my_antivirals.ranker.rank_combinations(combinations_with_distances, combinations_with_distances_reduce);

    // STEP 8
    my_antivirals.mutants.generate_mutants(sequence, combinations_with_distances_reduce, mutants_list);

    MutantContainer::const_iterator it_mutants;
    for (it_mutants = mutants_list.begin(); it_mutants != mutants_list.end(); ++it_mutants)
        // The originial sequence, should be always different to the generated
        ASSERT_NE(sequence, it_mutants->second);

    // According to the information we have, the size of mutants list should be this
    ASSERT_EQ(mutants_list.size(), 34);

    // This is neccesary to print the correct information
    algorithm_step = 8;
}